import cv2
import numpy as np
from collections import deque   # 高效的双端队列，用于循环缓冲
from datetime import datetime
import threading
import os
from typing import Optional
import time


class VideoRecorder:
    def __init__(self, config_loader):
        self.config = config_loader.get_app_config()        # 获取应用配置字典
        self.recording_config = self.config['recording']    # 获取录制配置（如前后录制时长、输出路径等）
        self.video_config = self.config['video']            # 获取视频配置（如帧率、分辨率等）

        # 录制参数
        self.pre_seconds = self.recording_config['pre_seconds']     # 预录制时间（危险发生前多少秒）
        self.post_seconds = self.recording_config['post_seconds']   # 后录制时间（危险解除后要继续录制的秒数）
        self.max_duration = self.recording_config['max_duration']   # 最大录制时长
        self.output_path = self.recording_config['output_path']     # 输出路径

        # 循环缓冲区，用于保存最近的帧，实现“预录制”
        self.buffer_size = self.pre_seconds * self.video_config['fps']      # 缓冲区大小=预录制秒数*帧率
        self.frame_buffer = deque(maxlen=self.buffer_size)                  # 创建定长队列，自动丢弃最早的帧

        # 录制状态
        self.is_recording = False               # 当前是否正在录制
        self.danger_detected_time = None        # 最近一次检测到危险的时间
        self.current_writer = None              # 当前的cv2.VideoWriter对象
        self.recording_thread = None            # 录制线程对象
        self.frames_to_record = []              # 当前要写入视频的帧列表

        # 录制控制
        self.last_recording_end_time = 0            # 上一次录制结束的时间戳
        self.min_recording_interval = 10            # 两次录制之间的最小间隔（秒），防止频繁录制
        self.recording_start_time = None            # 本次录制开始的时间戳
        self.post_recording_frames = 0              # 危险解除后已录制的帧数
        self.max_post_frames = self.post_seconds * self.video_config['fps']         # 后录制最大帧数

        # 确保输出目录存在
        os.makedirs(self.output_path, exist_ok=True)


    def add_frame(self, frame: np.ndarray, has_danger: bool = False):
        """添加帧到缓冲区，参数：frame: 帧，has_danger: 是否检测到危险"""
        self.frame_buffer.append((frame.copy(), time.time()))   # 将帧添加到缓冲区，同时记录时间戳

        # 如果正在录制
        if self.is_recording:
            current_time = time.time()                          # 获取当前时间戳
            elapsed_time = current_time - self.recording_start_time         # 计算录制开始后的时间

            # 检查是否超过最大录制时长
            if elapsed_time > self.max_duration:
                print(f"[录制] 达到最大时长 {self.max_duration}秒，停止录制")     # 打印最大时长
                self.stop_recording()       # 停止录制
                return

            # 添加帧到录制队列
            self.frames_to_record.append(frame.copy())

            # 如果没有危险，计算后续帧
            if not has_danger:
                self.post_recording_frames += 1

                # 如果后续帧达到设定值，停止录制
                if self.post_recording_frames >= self.max_post_frames:
                    print(f"[录制] 危险解除，已录制 {elapsed_time:.1f}秒")
                    self.stop_recording()
            else:
                # 如果又检测到危险，重置后续帧计数
                self.post_recording_frames = 0

    def trigger_recording(self):
        """触发录制（检测到危险时调用）"""
        current_time = time.time()      # 获取当前时间戳

        # 如果正在录制，延长录制时间
        if self.is_recording:
            # 重置后续帧计数，延长录制，危险再次出现后，继续录制
            self.post_recording_frames = 0
            return

        # 检查是否距离上次录制结束时间太近（小于最小录制间隔10s），防止频繁录制
        if current_time - self.last_recording_end_time < self.min_recording_interval:
            return

        # 否则，开始新的录制
        self.is_recording = True                    # 标记为正在录制
        self.recording_start_time = current_time    # 记录本次录制开始的时间戳
        self.danger_detected_time = current_time    # 记录最近一次检测到危险的时间戳
        self.post_recording_frames = 0              # 重置后续帧计数

        # # 将循环缓冲区中的帧（预录制内容）复制到录制队列，保证危险发生前的画面也被保存
        self.frames_to_record = [frame[0].copy() for frame in self.frame_buffer]

        # 启动一个新线程，异步保存视频，避免阻塞主流程
        self.recording_thread = threading.Thread(target=self._save_video)
        self.recording_thread.daemon = True  # 设置为守护线程，主程序退出时自动退出
        self.recording_thread.start()

        print(f"[录制] 开始录制危险视频...")

    def _save_video(self):
        """保存视频的线程函数"""
        try:
            # 生成文件名，格式为“危险_YYYYMMDD_HHMMSS.mp4”
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = os.path.join(self.output_path, f"danger_{timestamp}.mp4")    # 拼接输出路径和文件名

            # 创建视频写入器，设置编码器、帧率、分辨率
            fourcc = cv2.VideoWriter_fourcc(*'avc1')        # 设置编码器为mp4v 。avc1
            fps = self.video_config['fps']                  # 获取帧率
            frame_size = (self.video_config['frame_width'], self.video_config['frame_height'])  # 获取分辨率

            self.current_writer = cv2.VideoWriter(filename, fourcc, fps, frame_size)            # 创建视频写入器

            # 写入已有的帧
            frame_index = 0
            while self.is_recording: # 是否正在录制
                while frame_index < len(self.frames_to_record): # 是否还有帧
                    frame = self.frames_to_record[frame_index]
                    if frame.shape[:2] != (frame_size[1], frame_size[0]):   # 如果帧的尺寸与设置的不一致，则调整
                        frame = cv2.resize(frame, frame_size)               # 调整帧的尺寸
                    self.current_writer.write(frame)            # 写入帧
                    frame_index += 1                         # 计数
                if self.is_recording:
                    time.sleep(0.1)

            print(f"录制完成: {filename}， 共{frame_index}帧")

        except Exception as e:
            print(f"录制视频错误: {e}")
        finally:
            if self.current_writer:                 # 检测是否存在视频写入器
                self.current_writer.release()       # 释放视频写入器
                self.current_writer = None          # 清空当前写入器
            self.frames_to_record.clear()           # 清空录制队列
            self.is_recording = False               # 标记为停止录制


    def stop_recording(self):
        """停止录制"""
        self.is_recording = False                   # 标记为停止录制
        self.last_recording_end_time = time.time()  # 记录本次录制结束的时间戳

        if self.current_writer:
            self.current_writer.release()           # 释放视频写入器
            self.current_writer = None              # 清空当前写入器

        # 不立即清空，让保存线程完成
        # self.frames_to_record.clear()

